package org.miyuki4j.cfgtool;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;

/**
 * @Desc
 * @Author miyuki4j
 * @Date 2025/7/24/星期四 11:31
 * @Version 1.0
 */
@Slf4j
public class TableUtil {

    public static Map<Class<? extends Conf>, List<Conf>> loadConfs() throws Exception {
        TableDataExchangerEnum importType = TableConfig.get(TableConfig.ConfigKeyEnum.DATA_IMPORT_TYPE);
        return loadConfs(TableConfig.get(TableConfig.ConfigKeyEnum.DATA_IMPORT_DIR), importType);
    }

    public static Map<Class<? extends Conf>, List<Conf>> loadConfs(String confDir, TableDataExchangerEnum... exchangeTypes) throws Exception {
        return loadConfs(DefaultConfLoader.INSTANCE, confDir, exchangeTypes);
    }

    public static Map<Class<? extends Conf>, List<Conf>> loadConfs(ConfLoader confLoader, String confDir, TableDataExchangerEnum... exchangeTypes) throws Exception {
        return confLoader.loadConfs(importDatas(confDir, exchangeTypes));
    }

    public static List<TableImportData> importDatas() {
        String importDir = TableConfig.get(TableConfig.ConfigKeyEnum.DATA_IMPORT_DIR);
        TableDataExchangerEnum importType = TableConfig.get(TableConfig.ConfigKeyEnum.DATA_IMPORT_TYPE);
        if (importType == TableDataExchangerEnum.ALL) {
            return importDatas(importDir, TableDataExchangerEnum.values());
        } else {
            return importDatas(importDir, importType);
        }
    }

    public static List<TableImportData> importDatas(String importDir, TableDataExchangerEnum... exchangeTypes) {
        File file = new File(importDir);
        List<TableImportData> tableImportData = new ArrayList<>();
        recursiveImportDatas(tableImportData, file, exchangeTypes);
        return tableImportData;
    }

    public static void exportDatas() {
        String inputDir = TableConfig.get(TableConfig.ConfigKeyEnum.INPUT_DIR);
        String exportDir = TableConfig.get(TableConfig.ConfigKeyEnum.DATA_EXPORT_DIR);
        TableDataExchangerEnum exportType = TableConfig.get(TableConfig.ConfigKeyEnum.DATA_EXPORT_TYPE);
        if (exportType == TableDataExchangerEnum.ALL) {
            exportDatas(inputDir, exportDir, TableDataExchangerEnum.values());
        } else {
            exportDatas(inputDir, exportDir, exportType);
        }
    }

    public static void exportDatas(String inputDir, String exportDir, TableDataExchangerEnum... exchangeTypes) {
        // 1.读取数据
        LocalDateTime start = LocalDateTime.now();
        log.info("数据读取开始, time={}", LocalDateTime.now());
        List<TableData> tableDatas = readDatas(inputDir);
        LocalDateTime end = LocalDateTime.now();
        log.info("数据读取结束, time={}, cost={}", end, Duration.between(start, end).toMillis());

        // 2.验证数据
        start = LocalDateTime.now();
        log.info("数据验证开始, time={}", start);
        for (TableData tableData : tableDatas) {
            tableData.verify();
        }
        end = LocalDateTime.now();
        log.info("数据验证结束, time={}, cost={}", end, Duration.between(start, end).toMillis());

        // 3.导出数据
        start = LocalDateTime.now();
        log.info("数据导出开始, time={}", start);
        for (TableDataExchangerEnum exchangeType : exchangeTypes) {
            TableDataExporter tableDataExporter = exchangeType.getExporter();
            for (TableData tableData : tableDatas) {
                tableDataExporter.exportData(exportDir, tableData);
            }
        }
        end = LocalDateTime.now();
        log.info("数据导出结束, time={}, cost={}", LocalDateTime.now(), Duration.between(start, end).toMillis());
    }

    public static void generate() {
        String inputDir = TableConfig.get(TableConfig.ConfigKeyEnum.INPUT_DIR);
        String codeGeneratedDir = TableConfig.get(TableConfig.ConfigKeyEnum.CODE_GENERATE_DIR);
        CodeGeneratorEnum codeGeneratorEnum = TableConfig.get(TableConfig.ConfigKeyEnum.CODE_GENERATOR_TYPE);
        generate(inputDir, codeGeneratedDir, codeGeneratorEnum);
    }

    public static void generate(String inputDir, String codeGeneratedDir, CodeGeneratorEnum codeGeneratorEnum) {
        LocalDateTime start = LocalDateTime.now();
        log.info("代码生成-读取元数据-开始, now:{}", start);
        List<TableMeta> tableMetas = readMetas(inputDir);
        for (TableMeta tableMeta : tableMetas) {
            tableMeta.parse();
        }
        LocalDateTime end = LocalDateTime.now();
        log.info("代码生成-读取元数据-结束, now:{}, cost:{}", end, Duration.between(start, end).toMillis());
        CodeGenerator codeGenerator = codeGeneratorEnum.createGenerator();
        start = LocalDateTime.now();
        log.info("代码生成-开始, now:{}", start);
        for (TableMeta tableMeta : tableMetas) {
            codeGenerator.generate(codeGeneratedDir, tableMeta);
        }
        end = LocalDateTime.now();
        log.info("代码生成-结束, now:{}, cost:{}", end, Duration.between(start, end).toMillis());
        codeGenerator.exit();
    }

    public static List<TableMeta> readMetas() {
        return readMetas(TableConfig.get(TableConfig.ConfigKeyEnum.INPUT_DIR));
    }

    public static List<TableData> readDatas() {
        return readDatas(TableConfig.get(TableConfig.ConfigKeyEnum.INPUT_DIR));
    }

    public static List<TableMeta> readMetas(String dir) {
        File file = new File(dir);
        List<TableMeta> tableMetas = new ArrayList<>();
        recursiveRead(tableMetas, file, TableReader::readMeta);
        return tableMetas;
    }

    public static List<TableData> readDatas(String dir) {
        File file = new File(dir);
        List<TableData> tableDatas = new ArrayList<>();
        recursiveRead(tableDatas, file, TableReader::readData);
        return tableDatas;
    }

    private static void recursiveImportDatas(List<TableImportData> tableImportDatas, File file, TableDataExchangerEnum... exchangeTypes) {
        if (!file.exists()) {
            return;
        }
        if (file.isFile()) {
            for (TableDataExchangerEnum exchangeType : exchangeTypes) {
                if (exchangeType == TableDataExchangerEnum.ALL) {
                    continue;
                }
                if (exchangeType.isFileType(file.getName())) {
                    TableImportData tableImportData = exchangeType.getImporter().importData(file);
                    if (tableImportData != null) {
                        tableImportDatas.add(tableImportData);
                    }
                }
            }
        } else if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    recursiveImportDatas(tableImportDatas, f, exchangeTypes);
                }
            }
        } else {
            throw new IllegalArgumentException("file is not a file or directory: " + file.getAbsolutePath());
        }
    }

    private static <T> void recursiveRead(final List<T> list, final File file, final BiFunction<TableReader, File, T> func) {
        if (!file.exists()) {
            return;
        }
        if (file.isFile()) {
            list.add(read(file, func));
        } else if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    recursiveRead(list, f, func);
                }
            }
        } else {
            throw new IllegalArgumentException("file is not a file or directory: " + file.getAbsolutePath());
        }
    }

    private static <T> T read(File file, final BiFunction<TableReader, File, T> func) {
        String[] fileNames = file.getName().split("\\.");
        String fileType = fileNames[fileNames.length - 1];
        TableReaderEnum type = TableReaderEnum.of(fileType);
        Objects.requireNonNull(type, String.format("文件路径：%s, 不支持的文件类型:%s", file.getAbsolutePath(), fileType));
        T t = func.apply(type.getReader(), file);
        Objects.requireNonNull(t, String.format("文件路径：%s, 读取文件失败", file.getAbsolutePath()));
        return t;
    }
}
